#include <concepts>
#include <coroutine>
#include <variant>
#include <exception>
#include <iostream>
#include <vector>
#include <string>
#include <chrono>
#include <thread>
using namespace std::chrono_literals;

template <typename T>
struct Generator
{
    struct promise_type;
    using handle_type = std::coroutine_handle<promise_type>;
    struct promise_type
    {
        T value_;
        std::exception_ptr exception_;
        ~promise_type()
        {
            std::cerr << "promist_type dtor\n";
        }
        Generator get_return_object()
        {
            return Generator(handle_type::from_promise(*this));
        }
        std::suspend_always initial_suspend()
        {
            return {};
        }
        std::suspend_always final_suspend() noexcept
        {
            return {};
        }
        template <std::convertible_to<T> From>
        std::suspend_always yield_value(From &&from)
        {
            value_ = std::forward<From>(from);
            return {};
        }
        void unhandled_exception()
        {
            exception_ = std::current_exception();
        }
        void return_void()
        {
        }
    };
    handle_type h_;
    Generator(handle_type h) : h_(h)
    {
    }
    ~Generator()
    {
        h_.destroy();
    }
    explicit operator bool()
    {
        fill();
        return !h_.done();
    }
    T operator()()
    {
        fill();
        full_ = false;
        return std::move(h_.promise().value_);
    }

  private:
    bool full_{false};
    void fill()
    {
        if (!full_)
        {
            h_();
            if (h_.promise().exception_)
            {
                std::rethrow_exception(h_.promise().exception_);
            }
            full_ = true;
        }
    }
};

Generator<uint32_t> counter()
{
    for (uint32_t i = 0; i < 5; ++i)
    {
        co_yield i;
    }
}

Generator<std::string> hello()
{
    std::vector<std::string> strs{"Welcome\n",
                                  "Hello\n",
                                  "World\n",
                                  "This\n",
                                  "Is\n",
                                  "C++20\n",
                                  "Coroutine\n"};

    for (int i = 0; i < strs.size(); ++i)
    {
        co_yield strs[i];
    }
}

void test1()
{
    auto gen = counter();
    while (gen)
    {
        std::cout << "Counter: " << gen() << std::endl;
    }
}

void test2()
{
    auto gen = hello();
    while (gen)
    {
        std::cout << "App says: " << gen() << std::endl;
    }
}

int main(int argc, char **argv)
{
    test1();
    test2();
    return 0;
}
